//
//  main.cpp
//  
//
//  Created by Kun Zhao on 12/07/03.
//  Copyright (c) 2012 Kyoto University. All rights reserved.
//

#include <iostream>
#include <kvs/PointObject>
#include <kvs/PointImporter>
#include <kvs/glew/ParticleVolumeRenderer>
#include <kvs/glut/Application>
#include <kvs/glut/Screen>
#include <kvs/BitArray>

typedef unsigned char PTtype;
kvs::glut::Timer* glut_time;

kvs::BitArray* CTImporter( std::string filename, long long CT_length )
{
    kvs::BitArray* CTbit = new kvs::BitArray( CT_length );
    FILE* fp;
    fp = fopen( filename.c_str(), "rb" );
    fread( CTbit->pointer(), 1, CTbit->byteSize(), fp );
    fclose( fp );
    return( CTbit ); 
}

std::string CreateString( std::string filename, int i, std::string extension )
{
    char buf[10];
    sprintf( buf, "%03d", i );
    return( filename + std::string( buf ) + extension );
}

class TimerEvent : public kvs::TimerEventListener
{
    size_t time_step;
    kvs::PointObject** m_object;
    size_t m_step;
public:
    TimerEvent( kvs::PointObject** object, size_t step ) : m_object( object ), m_step( step ){ time_step = 0; }
    void update( kvs::TimeEvent* event )
    {
        kvs::glew::ParticleVolumeRenderer* renderer = new kvs::glew::ParticleVolumeRenderer();
        renderer->setName( "renderer" );
//        renderer->setShader( kvs::Shader::Phong( 0.5, 0.5, 0.8, 15.0 ) );
        renderer->setRepetitionLevel( 144 );
        
        screen()->objectManager()->change( "object", m_object[time_step++], false );
        screen()->rendererManager()->change( "renderer", renderer, true );
        std::cout << "\r" << time_step <<std::flush;
        screen()->redraw();
        if( time_step == m_step ) 
        {
            time_step = 0;
//            glut_time->stop();
        }
    }
};

int main( int argc, char** argv )
{
    kvs::glut::Application app( argc, argv );
    int step = 3;
    std::string filename = "FS85000CT_";
    
    // load particle data
    kvs::PointObject* object[step];
    for( int i = 0; i < step; i++)
    {
        object[i] = new kvs::PointImporter( CreateString( filename, i, ".kvsml" ) );
        object[i]->setName( "object" );
    }
    size_t number_of_cells;
    
    // load PT
    FILE* fp;
    fp = fopen( "PT.bin", "rb" );
    fseek( fp, 0, SEEK_END );
    long long length = ftell( fp );
    rewind( fp );
    char* PT_buf = new char[length];
    fread( PT_buf, 1, length, fp );
    long long PT_length = ( length - 4 ) / sizeof(PTtype);
    PTtype* PT = new PTtype[PT_length];
    memcpy( &number_of_cells, PT_buf, 4 );
    memcpy( PT, PT_buf + 4, length - 4 );
    delete PT_buf;

    // load CT
    kvs::BitArray* CT = CTImporter( "CTbit.bin", number_of_cells * ( step - 1 ) );    
    
    size_t loc_CT = 0;
    size_t loc_PT = 0;
    loc_PT += number_of_cells;
    PTtype* PT_previsou = new PTtype[number_of_cells];
    memcpy( PT_previsou, PT, number_of_cells * sizeof( PTtype ) );
    PTtype* PT_current = new PTtype[number_of_cells];
        
    // set the reconstrucion object
    kvs::PointObject* object_rec[step];
    object_rec[0] = object[0];
    object_rec[0]->setName( "object" );
    object_rec[0]->updateMinMaxCoords();
    kvs::Vector3f min_value = object_rec[0]->minObjectCoord();
    kvs::Vector3f max_value = object_rec[0]->maxObjectCoord();
    kvs::Vector3f min_ext = object_rec[0]->minExternalCoord();
    kvs::Vector3f max_ext = object_rec[0]->maxExternalCoord();

    std::cout << *object_rec[0] << std::endl;
    
    // reconstruct the point object by copying the particles from the previous step or current step depending on the coherence in CT
    for( int i = 1; i < step; i++ )
    {
        object_rec[i] = new kvs::PointObject();
//        // test
//        object_rec[i] = object[i];
        object_rec[i]->setName( "object" );

        const kvs::Real32* coords_current = object[i]->coords().pointer();
        const kvs::UInt8* colors_current = object[i]->colors().pointer();
        const kvs::Real32* normals_current = object[i]->normals().pointer();
        
        const kvs::Real32* coords_previous = object_rec[i-1]->coords().pointer();
        const kvs::UInt8* colors_previous = object_rec[i-1]->colors().pointer();
        const kvs::Real32* normals_previous = object_rec[i-1]->normals().pointer();
        
        kvs::Real32* coords_new = new kvs::Real32[number_of_cells * 3];
        kvs::UInt8* colors_new = new kvs::UInt8[number_of_cells * 3];
        kvs::Real32* normals_new = new kvs::Real32[number_of_cells * 3];
        
        unsigned int coord_index_previous = 0;
        unsigned int color_index_previous = 0;
        unsigned int normal_index_previous = 0;
        
        unsigned int coord_index_new = 0;
        unsigned int color_index_new = 0;
        unsigned int normal_index_new = 0;
        
        unsigned int coord_index_current = 0;
        unsigned int color_index_current = 0;
        unsigned int normal_index_current = 0;
        
        for( size_t index = 0; index < number_of_cells; index ++ )
        {
            size_t nparticles_in_cell;
            if( CT->test( index + loc_CT ) ) // there is a coherence
            {
                nparticles_in_cell = PT_previsou[index];
                // copy the particles from the previous step
                for( size_t pindex = 0; pindex < nparticles_in_cell; pindex++ )
                {
                    coords_new[coord_index_new++] = coords_previous[coord_index_previous++];
                    coords_new[coord_index_new++] = coords_previous[coord_index_previous++];
                    coords_new[coord_index_new++] = coords_previous[coord_index_previous++];
                    
                    colors_new[color_index_new++] = colors_previous[color_index_previous++];
                    colors_new[color_index_new++] = colors_previous[color_index_previous++];
                    colors_new[color_index_new++] = colors_previous[color_index_previous++];
                    
                    normals_new[normal_index_new++] = normals_previous[normal_index_previous++];
                    normals_new[normal_index_new++] = normals_previous[normal_index_previous++];
                    normals_new[normal_index_new++] = normals_previous[normal_index_previous++];
                    
                }
            }
            else // no coherence
            {
                nparticles_in_cell = PT[loc_PT++];
                // copy the particles from the current step
                for( size_t pindex = 0; pindex < nparticles_in_cell; pindex++ )
                {
                    coords_new[coord_index_new++] = coords_current[coord_index_current++];
                    coords_new[coord_index_new++] = coords_current[coord_index_current++];
                    coords_new[coord_index_new++] = coords_current[coord_index_current++];
                    
                    colors_new[color_index_new++] = colors_current[color_index_current++];
                    colors_new[color_index_new++] = colors_current[color_index_current++];
                    colors_new[color_index_new++] = colors_current[color_index_current++];
                    
                    normals_new[normal_index_new++] = normals_current[normal_index_current++];
                    normals_new[normal_index_new++] = normals_current[normal_index_current++];
                    normals_new[normal_index_new++] = normals_current[normal_index_current++];
                    
                }
                coord_index_previous += PT_previsou[index] * 3;
                color_index_previous += PT_previsou[index] * 3;
                normal_index_previous += PT_previsou[index] * 3;
            }
            // build the current PT
            PT_current[index] = nparticles_in_cell;
        }
        loc_CT += number_of_cells;
        PT_previsou = PT_current;
        
        kvs::ValueArray<kvs::Real32> coords( coords_new, coord_index_new );
        kvs::ValueArray<kvs::UInt8> colors( colors_new, color_index_new );
        kvs::ValueArray<kvs::Real32> normals( normals_new, normal_index_new );
        
        object_rec[i]->setCoords( coords );
        object_rec[i]->setColors( colors );
        object_rec[i]->setNormals( normals );
//        object_rec[i]->updateMinMaxCoords();
        object_rec[i]->setMinMaxObjectCoords( min_value, max_value );
        object_rec[i]->setMinMaxExternalCoords( min_value, max_value );
        object_rec[i]->updateNormalizeParameters();
        std::cout << *object_rec[i] << std::endl;

    }

    
    kvs ::glut::Screen screen( &app );
    TimerEvent time_event( object_rec, 3 );
    glut_time = new kvs::glut::Timer( 50 );

//    screen.show();
    kvs::glew::ParticleVolumeRenderer* renderer = new kvs::glew::ParticleVolumeRenderer();
    renderer->setRepetitionLevel( 144 );
    renderer->setName( "renderer" );
    screen.registerObject( object_rec[0], renderer );
    screen.addTimerEvent( &time_event, glut_time );
    screen.show();
    
    return( app.run() );   
}